package obecto.validation
{
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	
	import obecto.core.IEnabled;
	import obecto.event.ValidatorEvent;
	import obecto.validation.data.IDataValidator;

	[DefaultProperty("dataValidators")]
	[Event(name="valid", type="obecto.event.ValidatorEvent")]
	[Event(name="invalid", type="obecto.event.ValidatorEvent")]
	[Event(name="change", type="obecto.event.ValidatorEvent")]
	public class Validator extends EventDispatcher implements IValidator, IInteractiveValidator, IEnabled
	{
		private var _source : EventDispatcher;
		public function set source(value : EventDispatcher) : void 
		{
			if (value != null && value != _source)
			{
				_source = value;
				attachSourceListeners();
			}
		}
		
		public var changeEvent : String;
		public var focusInEvent : String;
		public var focusOutEvent : String;
		
		public var error : MovieClip;
		public var errorHalo : MovieClip;
			
		private var _dataValidators : Array;
		[ArrayElementType("obecto.validation.data.IDataValidator")]
		public function set dataValidators(value : Array) : void 
		{
			if (value != null)
			{
				_dataValidators = value;
				attachDataValidatorsListeners();
			}
		}
		
		private var _valid : Boolean = true;
		[Bindable(event="validChanged")]
		public function get valid() : Boolean
		{
			return _valid;
		}
		
		//****************************************
		// IInteractiveValidator implementation:
		//****************************************
		private var _changed : Boolean = false;
		public function get changed() : Boolean
		{
			return _changed;
		}
		
		[Bindable(event="validChanged")]
		[Bindable(event="sourceChanged")]
		public function get changedAndValid() : Boolean
		{
			return _changed && _valid;
		}
		
		//****************************************
		// IEnabled implementation:
		//****************************************
		private var _enabled : Boolean = true;
		public function set enabled(value : Boolean) : void
		{
			if (value != _enabled)
			{
				_enabled = value;
				if (_enabled)
				{
					validate();
				}
			}
		}
		public function get enabled():Boolean
		{
			return _enabled;
		}
		
		private var _active : Boolean = false;
		private function set active(value : Boolean) : void
		{
			if (_enabled && value != _active)
			{
				_active = value;
				if (_active)
				{
					updateErrorDisplay();
				}
				else
				{
					validate(); // performs validation on focus out
				}
			}
		}
		private function get active() : Boolean
		{
			return _active;
		}
		
		private var currentInvalidValidator : IDataValidator;
		private var pendingInvalidValidator : IDataValidator;
		
		public function Validator(target : IEventDispatcher = null)
		{
			super(target);
		}
		
		public function validate(quiet : Boolean = false) : void
		{
			if (_enabled)
			{
				validateDataValidators(quiet);
				
				var oldValid : Boolean = _valid;
				var newValid : Boolean = (pendingInvalidValidator == null);
				_valid = newValid;
				
				var event : ValidatorEvent;
				if (newValid)
				{
					event = new ValidatorEvent(ValidatorEvent.VALID);
				}
				else
				{
					event = new ValidatorEvent(ValidatorEvent.INVALID);
				}
				
				if (!quiet && newValid != oldValid)
				{
					dispatchEvent(event);
				}
				
				updateInvalidValidator();
				updateErrorDisplay(quiet);
			}
			
			dispatchEvent(new Event("validChanged")); // trigger all bindings to the valid-property
		}
		
		public function reset() : void
		{
			currentInvalidValidator = null;
			pendingInvalidValidator = null;
			_valid = true;
			_changed = false;
		}
		
		private function validateDataValidators(quiet : Boolean = false) : void 
		{
			pendingInvalidValidator = null;
			for each (var dataValidator : IDataValidator in _dataValidators)
			{
				dataValidator.validate(quiet);
				if (!dataValidator.valid)
				{
					pendingInvalidValidator = dataValidator;
					break;
				}
			}
		}
		
		private function updateInvalidValidator() : void
		{
			if (currentInvalidValidator == null)
			{
				currentInvalidValidator = pendingInvalidValidator;
			}
			else if (currentInvalidValidator.valid ||
				(_dataValidators.indexOf(pendingInvalidValidator) < 
				_dataValidators.indexOf(currentInvalidValidator)))
			{
				currentInvalidValidator = pendingInvalidValidator;
			}
		}
		
		private function updateErrorDisplay(quiet : Boolean = false) : void 
		{
			if (_valid)
			{
				hideError();
				hideErrorHalo();
			}
			else
			{
				if (active)
				{
					if (!quiet && _changed)
					{
						showError();
						showErrorHalo();
					}
				} 
				else
				{
					if (!quiet && _changed)
					{
						showErrorHalo();
					}
					hideError();
				}
			}
		}
		
		private function showError() : void 
		{
			if (error != null) 
			{
				updateError();
				error.visible = true;
			}
		}
		
		private function showErrorHalo() : void 
		{
			if (errorHalo != null) errorHalo.visible = true;
		}
		
		private function hideError() : void 
		{
			if (error != null) error.visible = false;
		}
		
		private function hideErrorHalo() : void
		{
			if (errorHalo != null) errorHalo.visible = false;
		}
		
		private function updateError() : void 
		{
			var textField : TextField = TextField(error.text);
			var background : MovieClip = MovieClip(error.background);
			
			textField.text = currentInvalidValidator.errorMessage;
			background.width = textField.width + 2 * 20;
		}
		
		private function attachSourceListeners() : void 
		{
			_source.addEventListener(changeEvent, handleChange);
			_source.addEventListener(focusInEvent, handleFocusIn);
			_source.addEventListener(focusOutEvent, handleFocusOut);
			_source.addEventListener(MouseEvent.ROLL_OVER, handleRollOver);
			_source.addEventListener(MouseEvent.ROLL_OUT, handleRollOut);
		}
		
		private function attachDataValidatorsListeners() : void 
		{
			for each (var dataValidator : IDataValidator in _dataValidators)
			{
				dataValidator.addEventListener(ValidatorEvent.VALID, handleValid);
				dataValidator.addEventListener(ValidatorEvent.INVALID, handleInvalid);
			}
		}
		
		private function handleValid(e : ValidatorEvent) : void 
		{
			validate();
		}
		
		private function handleInvalid(e : ValidatorEvent) : void 
		{
			validate();
		}
		
		private function handleChange(e : Event) : void 
		{
			validate();
			
			// here dispatch always change:
			_changed = true;
			dispatchEvent(new Event("sourceChanged"));
			dispatchEvent(new ValidatorEvent(ValidatorEvent.CHANGE));
		}
		
		private function handleFocusIn(e : Event) : void 
		{
			active = true;
		}
		
		private function handleFocusOut(e : Event) : void 
		{
			if (!_changed)
			{
				_changed = true;
			}
			validate();
			active = false;
		}
		
		private function handleRollOver(e : Event) : void 
		{
			if (!_valid && _changed)
			{
				showError();
			}
		}
		
		private function handleRollOut(e : Event) : void
		{
			if (!_active)
			{
				hideError();
			}
		}
		
	}
}